#include "edgeTraversal.hpp"
#include "uniMesh.hpp"

#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>

#include <numeric>
#include <iostream>
#include <vector>

using namespace hms;

/** @brief Test function collecting edge indices */
template<typename MeshType>
void getEdgeIndices(
	const MeshBase<MeshType>& mesh, Index e, std::vector<Index>& indices
){
	#ifdef _OPENMP
	#pragma omp critical (collectIndices)
	#endif
	{
		indices.push_back( mesh.edge(e).index() );
	}
}

TEST_CASE( "traverseEdges: Traversal dispatcher" ){
	UniMesh mesh {13, 16, 1.1, 1.2};
	using T = EdgeTraversal;

	/* check whether all edges have been visited exactly once */
	auto test = [&]( EdgeTraversal traversal ){
		std::vector<Index> edges, edgesCheck;
		
		edgesCheck.resize( mesh.nEdges() );
		std::iota( edgesCheck.begin(), edgesCheck.end(), 0 );

		traverseEdges(traversal, mesh, getEdgeIndices<decltype(mesh)>, edges);
		std::sort( edges.begin(), edges.end() );

		REQUIRE( edges == edgesCheck );
	};

	SECTION( "Ordered traversal (default)" ){
		test(T::ordered);
	}
	SECTION( "Rowwise traversal" ){
		test(T::rowwise);
	}
	SECTION( "Blockwise traversal" ){
		test(T::blockwise);
	}

}

TEST_CASE( " traverseEdgesSecondOrder: Traversal dispatcher "){
	using T = EdgeTraversal;
	UniMesh mesh1 {10, 10, 1.1, 1.2};
	mesh1.blocksize(4);
	UniMesh meshSmall {10, 8, 1.1, 1.2};
	meshSmall.blocksize(4);

	UniMesh mesh0 { 10, 10, 1.0, 1.0};
	mesh0.blocksize(3);



	UniMesh mesh2 {11, 11, 1.0, 1.0}; 
	mesh2.blocksize(3);
	UniMesh mesh3 {11, 12, 1.0, 1.0};
	mesh3.blocksize(4);
	UniMesh mesh4 {12, 11, 1.0, 1.0};
	mesh4.blocksize(4);
	UniMesh mesh5 {16, 16, 1.0, 1.0};
	mesh5.blocksize(4);
	UniMesh meshLarge {500, 500, 1.0, 1.0};
	meshLarge.blocksize(32);

		/* check whether all edges have been visited exactly once */
	auto test = [&]( EdgeTraversal traversal, const auto& mesh ){
		using MeshType = remove_qualifiers<decltype(mesh)>;
		std::vector<Index> edges, edgesCheck;

		edgesCheck.resize( mesh.nEdges() );
		std::iota( edgesCheck.begin(), edgesCheck.end(), 0 );

		Array<bool, 4,1> meshPartAdjacency {true, true, true, true};
		traverseEdgesSecondOrder(traversal, mesh, meshPartAdjacency,
			getEdgeIndices<MeshType>,
			getEdgeIndices<MeshType>, 
			edges
		);
		
		std::sort( edges.begin(), edges.end() );

		REQUIRE( edges == edgesCheck );
	};

	SECTION( "Ordered traversal (second order)" ){
		test(T::ordered_secondOrder, mesh1);
	}
	SECTION( "Rowwise traversal (second order)" ){
		test(T::rowwise_secondOrder, mesh1);
	} 
	SECTION ("Rowwise traversal (second order, seamed)"){
		test(T::rowwise_secondOrder_seamed, mesh1);
		test(T::rowwise_secondOrder_seamed, mesh2);
		test(T::rowwise_secondOrder_seamed, mesh3);
		test(T::rowwise_secondOrder_seamed, mesh4);
		test(T::rowwise_secondOrder_seamed, mesh5);
	}	
	SECTION( "Blockwise traversal (second order, seamless) " ){
		
		test(T::blockwise_secondOrder, mesh1);
		test(T::blockwise_secondOrder, mesh2);
		test(T::blockwise_secondOrder, mesh3);
		test(T::blockwise_secondOrder, mesh4);
		test(T::blockwise_secondOrder, mesh5);
		test(T::blockwise_secondOrder, meshSmall);

	}

	SECTION( "Blockwise traversal (second order, seamed)" ){

		test(T::blockwise_secondOrder_seamed, mesh0);
		test(T::blockwise_secondOrder_seamed, mesh1);
		test(T::blockwise_secondOrder_seamed, mesh2);
		test(T::blockwise_secondOrder_seamed, mesh3);
		test(T::blockwise_secondOrder_seamed, mesh4);
		test(T::blockwise_secondOrder_seamed, mesh5);
	
	}

}